import Layers from "@/lang/layers";
import FormWrapper from "./form-wrapper";
import AsyncValidator from "async-validator";

import {messages} from "./messages";
import snowflake from "@/lang/snowflake/index.js";

/**
 * 反序列化函数
 *
 * 与 format() 相反的函数，需要将数据转为组件可识别的格式。
 *
 * 比如：存储到数据库中的 key 值，在组件中使用，你需要转换成对应的 value 值。
 *
 * @param v {*} 后台响应数据
 * @return {*}  组件可识别的格式
 */
const parse = (v) => {
    return v;
}

/**
 * 序列化函数
 *
 * 与 parse() 相反的函数，将组件的返回值转换为后台可识别的格式。
 *
 * 比如：组件给的是 value 值，存储到数据库中，需要转换成对应的 key 值。
 *
 * @param v {*} 用户输入数据
 * @return {*}  后台可识别的格式
 */
const format = (v) => {
    return v;
}

/**
 * 通用表单接口
 *
 * 设计思路，类似于后台的 MVC 模式，form-model => form-input => view；
 */
class FormInput {
    constructor() {
        // 唯一 ID
        let id = snowflake.nextId();
        this._id = 'sea-input-' + id;

        // 输入组件类型
        this._type = undefined;

        // form 表单对象实体
        this._form = undefined;
        // 组件对象实体，输入框对象（node、jquery、vue对象，取决于当前平台）
        this._dom = undefined;
        // 布局对象实体，输入框位于什么样的容器中（node、jquery、vue对象，取决于当前平台）
        this._layout = undefined;

        /**
         * 激活状态，
         *
         * form-model 可以是一份全量的表单模型，界面渲染的时候，不一定会用上全部字段。
         * 只有完成激活，才会参序列化。
         *
         * @type {boolean}
         */
        this._active = false;

        // 字段名，提交表单时候用的字段名
        this._name = undefined;
        // 字段值，经过格式化，能被组件使用的值
        this._value = undefined;
        // 默认值，哑元元素，默认是空串
        this._default_value = '';
        // 标签值，展示在前端，能被用户看到的字段名
        this._label = undefined;
        // 占位符，用于提示的字段
        this._placeholder = undefined;
        // 可选的值，组件配置参数
        this._options = undefined;

        // 主题：表单布局，影响到组件的渲染、布局方式
        this._theme = '';
        // 栈格布局中，输入框 label 和 input 的宽度比例
        this._col = [8, 16];
        // 栈格布局中，输入框本身宽度占比
        this._span = 24;

        // 3大属性：隐藏、只读、禁用
        this._hidden = false;
        // 是否只读
        this._readonly = false;
        // 是否禁用
        this._disabled = false;


        // 是否必填
        this._require = false;
        // 启动输入校验
        this._usingValidator = false;
        // 数据校验规则，取决于校验框架，当前使用的是 {AsyncValidator}
        this._rules = undefined;
        // 输入值是否有效
        this._valid = undefined;
        // 输入值无效时，需要展示的错误提示
        this._message = undefined;


        // 数据类型，影响到数据序列化（未实现）
        this._classType = undefined;
        // 数据类型，影响到数据序列化（未实现）
        this._className = undefined;
        // 序列化函数，格式化输出，提交表单之前，要有简单的数据格式化功能，比如：将日期转成相对统一的格式
        this.format = format
        // 反序列化函数，对于组件的入参，要有简单的数据格式化功能，比如：把字符串转成数字
        this.parse = parse


        // 有考虑，但是暂不实现的设计：

        // Incorrect use of <label for=FORM_ELEMENT>
        // 抑制警告：因为 vue 组件异步渲染的因素，尽管有时候属性全对，仍然会产生警告，
        // 实在没办法的情况，需要有一些离谱的手段，强行压制代码警告，
        // 缺点：这可能会略微降低用户体验，比如：无法用 tab 键快速切换到下一个输入框
        // this.suppress_warning = true;

        // 组件 宽度 和 权重，暂时不增加这一设计，这样做显得封装有些过度了
        // this.width = undefined;
        // this.weight = undefined;
    }

    /**
     * 设置序列化函数，数据提交之前，经过默认的格式化处理，常用于日期的格式化
     *
     * @param fun {function}
     */
    setFormatter(fun) {
        this.format = fun
    }

    /**
     * 设置反序列化函数，将数据处理成能被组件识别的格式
     *
     * @param fun {function}
     */
    setParser(fun) {
        this.parse = fun
    }

    /**
     * 由模型转对象实体
     *
     * 覆盖已有的属性值
     *
     * @param model {any}
     */
    from(model) {
        // 关键字段
        this.name = model.name;
        this.label = model.label;
        this.value = model.value;
        this.require = model.require;

        // 布局风格
        this.col = model.col;
        this.span = model.span;

        // 显示隐藏
        this.hidden = model.hidden;

        // 3大属性：隐藏、只读、禁用
        this.hidden = model.hidden;
        // 是否只读
        this.readonly = model.readonly;
        // 是否禁用
        this.disabled = model.disabled;
    }

    /**
     * 将输入框打包成特定的组件
     */
    wrap(type) {
        if (this._type === undefined) {
            this._type = type;
            FormWrapper.wrap(this, type);
        }
    }

    /**
     * 获取序列化之后的值
     *
     * @return {*} 一般是字符串
     */
    getFormatValue() {
        let ret = this._value;
        if (Layers.isBlank(ret)) {
            return undefined;
        } else {
            return this.format(ret);
        }
    }

    /**
     * 清除数据
     *
     * 一般不是置为 undefined，而是置为缺省值
     */
    clear() {
        this.setValue(this._default_value);
    }

    /**
     * 设置可选项
     *
     * jquery 环境下，需要重新渲染下拉选项，vue 则通过侦听实现
     * @param v 例如：下拉框的数据字典
     */
    setOptions(v) {
        this._options = v;
    }

    /**
     * 隐藏组件
     */
    hide() {
        // do sth. in other environment
        this._hidden = true;
    }

    /**
     * 显示组件
     */
    show() {
        // do sth. in other environment
        this._hidden = false;
    }

    /**
     * 渲染对象实例，在原生 html 环境下，就是构造 node 对象，append 到容器中
     */
    create() {
        // do nothing in vue.
    }

    /**
     * 初始化，第一次置入控件的时候，需要进行初始化
     *
     * @param env {object} 环境参数，vue 下是 component（this）对象，jquery下是 $ 对象
     */
    awake(env) {
        // do nothing in vue
    }

    /**
     * 销毁对象，jquery环境下，通常写的是 dom.remove()
     */
    destroy() {
        // do nothing in vue
    }

    /**
     * 启动数据校验
     *
     * 第一次数据校验是特殊的，需要完成一些初始化操作
     *
     * @param val{boolean}
     */
    startupValidator(val) {
        this._usingValidator = val;
        if (val === true) {
            // 校验结果通知到布局和输入组件
            let ret = this.checkValidity();
            if (this._layout !== undefined && this._layout.onStartupValidator !== undefined) {
                this._layout.onStartupValidator(ret);
            }
            if (this._dom !== undefined && this._dom.onStartupValidator !== undefined) {
                this._dom.onStartupValidator(ret);
            }
            return ret;
        }
    }

    /**
     * 是否启用数据校验
     *
     * @return {boolean}
     */
    isStartupValidator() {
        return this._usingValidator === true;
    }

    /**
     * 组件输入值是否有效
     *
     * @returns {boolean} input value is valid
     */
    isValid() {
        if (this._active === false || this._hidden === true) {
            // 未激活、隐藏组件不参与校验
            return true;
        } else if (this.isStartupValidator()) {
            // 已经触发的校验器，直接获取校验结果即可
            return this.checkValidity();
        } else {
            // 首次校验是特殊的，校验器需要完成一些初始操作，界面也要产生联动效果
            return this.startupValidator(true);
        }
    }

    /**
     * 比较当前值是否有效，错误信息会直接赋值到 this.message
     *
     * @returns {boolean|undefined} 返回验证结果
     */
    checkValidity() {
        if (this.isStartupValidator()) {
            let input = this;
            let name = input.name, label = input.label, value = input.value;
            let res = true;
            // require 验证单独封装，require 是很特殊的校验，与 H5 默认功能挂钩，
            // require 设置为 true 的情况下，某些项目可能要求增加一个小红点，目前版本没有这个设计。
            if (input.require === true) {
                if (Layers.isEmpty(value)) {
                    this._message = '[' + label + ']不允许为空！';
                    res = false;
                }
            }
            // Validator 验证
            if (res === true && input._rules !== undefined) {
                res = this.checkValidityWithAsyncValidator(input._rules, value, input._label);
            }
            this._valid = res;
            return res;
        }
        // if didn't startup validator, return undefined.
    }

    /**
     * 使用 AsyncValidator 进行数据校验，代码参考自 element-ui
     *
     * @param rules 数据校验规则
     * @param value 需要验证的值
     * @param label 提示用的标签值
     * @returns {boolean} 返回验证结果
     */
    checkValidityWithAsyncValidator(rules, value, label) {
        // 规则描述
        const descriptor = {};
        if (Layers.isNotEmpty(rules)) {
            rules.forEach(rule => {
                delete rule.trigger;
            });
        }
        descriptor[label] = rules;

        // 数据模型
        const validator = new AsyncValidator(descriptor);
        const model = {};
        model[label] = value;

        // 导入汉化之后的错误提示包
        validator.messages(messages);

        // AsyncValidator.messages = messages;

        // 数据校验
        let res = true;
        validator.validate(model, {firstFields: true}, (errors, invalidFields) => {
            res = !errors;
            //错误提示
            this._message = errors ? errors[0].message : '';
        });
        return res;
    }

    // getter/setter ---------------------------------------------------------------

    /**
     * 输入组件类型
     *
     * @returns {String}
     */
    get type() {
        return this._type;
    }

    /**
     * 输入组件类型
     *
     * @param value {String}
     */
    set type(value) {
        this._type = value;
    }

    /**
     * 数据类型，影响到数据序列化
     *
     * @returns {*}
     */
    get classType() {
        return this._classType;
    }

    /**
     * 数据类型，影响到数据序列化
     *
     * @param value {String}
     */
    set classType(value) {
        this._classType = value;
    }

    /**
     * 数据类型，影响到数据序列化
     *
     * @returns {*}
     */
    get className() {
        return this._className;
    }

    /**
     * 数据类型，影响到数据序列化
     *
     * @param value {String}
     */
    set className(value) {
        this._className = value;
    }

    /**
     * form 表单对象实体
     *
     * @returns {{}}
     */
    get form() {
        return this._form;
    }

    /**
     * form 表单对象实体
     *
     * @param value {{}}
     */
    set form(value) {
        this._form = value;
    }

    /**
     * 输入框对象实体
     *
     * @returns {{}}
     */
    get dom() {
        return this._dom;
    }

    /**
     * 输入框对象实体
     *
     * @param value {{}}
     */
    set dom(value) {
        this._active = true;
        this._dom = value;
    }

    /**
     * 设置组件对象实体
     *
     * 设置的同时，会激活 form-input 对象
     *
     * @param dom {any}
     */
    setDom(dom) {
        this._active = true;
        this._dom = dom;
    }

    /**
     * 布局对象实体
     *
     * @returns {*}
     */
    get layout() {
        return this._layout;
    }

    /**
     * 布局对象实体
     *
     * @param value {{}}
     */
    set layout(value) {
        this._active = true;
        this._layout = value;
    }

    /**
     * 布局对象实体
     *
     * @param value {{}}
     */
    setLayout(value) {
        this._active = true;
        this._layout = value;
    }

    /**
     * 激活组件
     *
     * @returns {Boolean}
     */
    get active() {
        return this._active;
    }

    /**
     * 激活组件
     *
     * @param value {Boolean}
     */
    set active(value) {
        this._active = value;
    }

    /**
     * 内部生成的随机唯一 ID
     *
     * @returns {*}
     */
    get id() {
        return this._id;
    }

    /**
     * 内部生成的随机唯一 ID
     *
     * @param value {String}
     */
    set id(value) {
        this._id = value;
    }

    /**
     * 字段名，提交表单时候用的字段名
     *
     * @returns {String}
     */
    get name() {
        return this._name;
    }

    /**
     * 字段名，提交表单时候用的字段名
     *
     * @param value {String}
     */
    set name(value) {
        this._name = value;
    }

    /**
     * 字段值，经过格式化，能被组件使用的值
     *
     * @returns {*}
     */
    get value() {
        return this._value;
    }

    /**
     * 字段值，经过格式化，能被组件使用的值
     *
     * @param v {*}
     */
    set value(v) {
        const value = v === null || v === undefined ? this.defaultValue : v;
        if (this._value !== value) {
            this._value = value;
        }
    }

    /**
     * 字段值，经过格式化，能被组件使用的值
     *
     * @param v {*}
     */
    setValue(v) {
        const value = v === null || v === undefined ? this.defaultValue : v;
        if (this._value !== value) {
            this._value = value;
        }
    }

    /**
     * 设置一个经过格式化的值
     *
     * 值需要被反格式化，才能被组件识别，例如：数组会被转为逗号拼接的字符串
     * <p>
     * 如果不希望触发 change() 事件，需要调用这个函数，一般来说，代码上的赋值操作，不触发 change() 事件
     * @param v
     */
    setFormatValue(v) {
        const value = v === null || v === undefined ? this.defaultValue : this.parse(v);
        if (this._value !== value) {
            this._value = value;
        }
    }

    /**
     * 触发 change() 事件
     */
    change() {

        // 数据变化通知到组件对象，需要触发数据联动
        if (this._dom !== undefined) {
            this._dom.onChangeValue(this._value);
        }

        // 数据变化通知到布局对象，需要触发数据校验
        if (this._layout !== undefined) {
            this._layout.onChangeValue(this._value);
        }

        // 数据变化通知到表单对象，可能有需要触发查询的场景
        if (this._form !== undefined) {
            this._form.trigger(this._name, this._value);
        }
    }

    /**
     * 设置值
     *
     * 在需要触发 change() 事件的时候，需要调用这个函数，
     * 注意：如果 value 不变，但是内部元素发生变化，是不会触发 change() 事件的
     *
     * @param v
     */
    changeValue(v) {
        const value = v === null || v === undefined ? this.defaultValue : v;
        if (this._value !== value) {
            this._value = value;

            // 触发 change() 事件
            this.change();
        }
    }

    /**
     * 设置一个经过格式化的值
     *
     * 值需要被反格式化，才能被组件识别，例如：数组会被转为逗号拼接的字符串
     *
     * 如果希望触发 change() 事件，需要调用这个函数
     * @param v
     */
    changeFormatValue(v) {
        const value = v === null || v === undefined ? this.defaultValue : this.parse(v);
        if (this._value !== value) {
            this._value = value;

            // 触发 change() 事件
            this.change();
        }
    }

    /**
     * 默认值，哑元元素
     *
     * 表单组件，值一般不会是 undefined，比如输入框，它的默认值是空字符串。
     * 需要指定一个特定的值，表示用户未输入
     *
     * @returns {any}
     */
    get defaultValue() {
        return this._default_value;
    }

    /**
     * 默认值，哑元元素
     *
     * @param value {*} 默认值
     */
    set defaultValue(value) {
        this._default_value = value;
    }

    /**
     * 标签值，用于展示的字段中文名
     *
     * @returns {*}
     */
    get label() {
        return this._label;
    }

    /**
     * 标签值，用于展示的字段中文名
     *
     * @param value {String}
     */
    set label(value) {
        this._label = value;
    }

    /**
     * 占位符，用于提示的字段
     *
     * @returns {String}
     */
    get placeholder() {
        return this._placeholder;
    }

    /**
     * 占位符，用于提示的字段
     *
     * @param value {String}
     */
    set placeholder(value) {
        this._placeholder = value;
    }

    /**
     * 组件微调属性
     *
     * 比如：拉框的数据字典、日期组件的参数配置等
     *
     * @returns {*}
     */
    get options() {
        return this._options;
    }

    /**
     * 比如：拉框的数据字典、日期组件的参数配置等
     *
     * @param value
     */
    set options(value) {
        this._options = value;
    }

    /**
     * 主题：表单布局，影响到组件的渲染、布局方式
     *
     * @returns {String}
     */
    get theme() {
        return this._theme;
    }

    /**
     * 主题：表单布局，影响到组件的渲染、布局方式
     *
     * @param value {String}
     */
    set theme(value) {
        this._theme = value;
    }

    /**
     * 是否必填
     *
     * @returns {boolean}
     */
    get require() {
        return this._require;
    }

    /**
     * 是否必填
     *
     * @param require {boolean}
     */
    set require(require) {
        this._require = require;
    }


    /**
     * 组件 宽度 和 权重，暂时不增加这一设计，这样做显得封装有些过度了
     *
     * @returns {[Number]}
     */
    get col() {
        return this._col;
    }

    /**
     * 组件 宽度 和 权重，暂时不增加这一设计，这样做显得封装有些过度了
     *
     * @param value {[Number]}
     */
    set col(value) {
        this._col = value;
    }

    /**
     * 栈格布局中，输入框本身宽度占比
     *
     * @returns {String}
     */
    get span() {
        return this._span;
    }

    /**
     * 栈格布局中，输入框本身宽度占比
     *
     * @param value {String}
     */
    set span(value) {
        this._span = value;
    }

    /**
     * 是否隐藏，隐藏的组件，不参与表单校验
     *
     * @returns {Boolean}
     */
    get hidden() {
        return this._hidden;
    }

    /**
     * 是否隐藏，隐藏的组件，不参与表单校验
     *
     * @param value {Boolean}
     */
    set hidden(value) {
        this._hidden = value;
    }

    /**
     * 是否只读
     *
     * @returns {Boolean}
     */
    get readonly() {
        return this._readonly;
    }

    /**
     * 是否只读
     *
     * @param value {Boolean}
     */
    set readonly(value) {
        this._readonly = value;
    }

    /**
     * 是否禁用
     *
     * @returns {Boolean}
     */
    get disabled() {
        return this._disabled;
    }

    /**
     * 是否禁用
     *
     * @param value {Boolean}
     */
    set disabled(value) {
        this._disabled = value;
    }

    /**
     * 数据校验规则，取决于校验框架，当前使用的是 {AsyncValidator}
     *
     * @returns {[any]}
     */
    get rules() {
        return this._rules;
    }

    /**
     * 数据校验规则，取决于校验框架，当前使用的是 {AsyncValidator}
     *
     * @param value  {[any]}
     */
    set rules(value) {
        this._rules = value;
    }

    /**
     * 输入值是否有效
     *
     * 一般要考虑调用 isValid() 函数，当前函数只是返回最终校验的结果
     *
     * @see isValid()
     * @see checkValidity()
     * @returns {Boolean}
     */
    get valid() {
        return this._valid;
    }

    /**
     * 输入值是否有效
     *
     * @param value {Boolean}
     */
    set valid(value) {
        this._valid = value;
    }

    /**
     * 输入值无效时，需要展示的错误提示
     *
     * @returns {String}
     */
    get message() {
        return this._message;
    }


    /**
     * 输入值无效时，需要展示的错误提示
     *
     * @param value  {String}
     */
    set message(value) {
        this._message = value;
    }
}

export default FormInput
